﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using H2.Streams;
using H2.Streams.Sections;
using H2.DataTypes;
using System.IO;
using Mystery.SCNR;
using Mystery.SCNR.Scripts;
using Mystery.Unicode;

namespace Mystery
{
    public partial class Main : Form
    {
        MapStream map;

        string[] bipdTags;
        string[] modeTags;
        string[] sbspTags;

        PlacementSelector aiSquadPlacementSelector = new PlacementSelector();
        ScriptViewer scriptViewer = new ScriptViewer();

        public Main()
        {
            InitializeComponent();
            cboAIDifficulty.SelectedIndex = 0;
        }

        private void cmdStart_Click(object sender, EventArgs e)
        {
            gpOptions.Enabled = false;
            cmdStart.Enabled = false;

            map.ReOpen();

            //Go to Player Representations Reflexive
            map.Position = map.Tags[map.Index.GlobalsID, Tags.SearchType.ID].Offset + 0x138;
            int chunkCount = map.ReadReflexive();
            if (chunkCount != 4) throw new Exception("Incorrect Player Representaion Chunk Count! Map Might Be Corrupt!");

            //Write Player Representations
            WritePlayerRepresentations((string)cboPlayerUnit1.SelectedItem, (string)cboPlayerHands1.SelectedItem, (string)cboPlayerBody1.SelectedItem,
                                     (string)cboPlayerUnit2.SelectedItem, (string)cboPlayerHands2.SelectedItem, (string)cboPlayerBody2.SelectedItem);

            //Get Scnr Offset
            int scnrOffset = map.Tags[map.Index.ScnrID, Tags.SearchType.ID].Offset;

            //Go to SBSP Reflexive
            map.Position = scnrOffset + 528;
            chunkCount = map.ReadReflexive();
            int reflexiveStartOffset = (int)map.Position;

            //Locate SBSP Information
            int sbspTagID = map.Tags["sbsp", (string)cboSBSP.SelectedItem].ID;
            int sbspChunkIndex = -1;
            byte[][] sbspChunkBuffers = new byte[chunkCount][];
            for (int i = 0; i < chunkCount; i++)
            {
                sbspChunkBuffers[i] = map.ReadBytesAt(reflexiveStartOffset + (i * 68), 68, false);
                if (map.ReadInt32At(reflexiveStartOffset + (i * 68) + 20, false) == sbspTagID)
                    sbspChunkIndex = i;
            }
            if (sbspChunkIndex == -1) throw new Exception("Unable To Locate SBSP Information!");

            SBSP sbsp = new SBSP();
            SbspInfo sbspInfo = map.Sbsp[sbspTagID, Tags.SearchType.ID];
            int magic = sbspInfo.Magic;
            map.Position = sbspInfo.Offset;
            sbsp.ReadFrom(map, magic);

            //Write SBSP Information To First Chunk
            map.WriteAt(reflexiveStartOffset, sbspChunkBuffers[sbspChunkIndex], false);
            for (int i = 0; i < chunkCount; i++)
                if(i != sbspChunkIndex) map.Write(sbspChunkBuffers[i]);

            Placement[] bipedPlacements = new Placement[0];
            if (cbConvertBipedSpawns.Checked)
            {
                //If Selected By User, Read Weapon Placements
                bipedPlacements = Placement.ReadBipeds(map, scnrOffset, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex);
            }

            //Read Scenery Palette
            Palette[] sceneryPalette = new Palette[map.ReadReflexiveAt(scnrOffset + 88)];
            Palette.ReadScenery(map, sceneryPalette);

            Placement[] weaponPlacements = new Placement[0];
            Palette[] weaponPalette = new Palette[0];
            if (cbConvertWeapSpawns.Checked)
            {
                //If Selected By User, Read Weapon Placements
                weaponPlacements = Placement.ReadWeapons(map, scnrOffset, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex);

                //Read Weapon Palette
                weaponPalette = new Palette[map.ReadReflexiveAt(scnrOffset + 152)];
                Palette.ReadWeapons(map, weaponPalette);
            }

            Placement[] vehiclePlacements = new Placement[0];
            Palette[] vehiclePalette = new Palette[0];
            if (cbConvertVehiSpawns.Checked)
            {
                //If Selected By User, Read Vehicle Placements
                vehiclePlacements = Placement.ReadVehicles(map, scnrOffset, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex);

                //Read Vehicle Palette
                vehiclePalette = new Palette[map.ReadReflexiveAt(scnrOffset + 120)];
                Palette.ReadVehicles(map, vehiclePalette);
            }

            Placement[] equipmentPlacements = new Placement[0];
            Palette[] equipmentPalette = new Palette[0];
            if (cbConvertEquipSpawns.Checked)
            {
                //If Selected By User, Read Vehicle Placements
                equipmentPlacements = Placement.ReadEquipment(map, scnrOffset, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex);

                //Read Vehicle Palette
                equipmentPalette = new Palette[map.ReadReflexiveAt(scnrOffset + 136)];
                Palette.ReadEquipment(map, equipmentPalette);
            }

            string TagOutputDir = Path.Combine(Path.GetDirectoryName(map.FilePath), "Tags");
            List<string> outputFiles = new List<string>();
            Scenario scnr = new Scenario();

            if (cbAddFlagBasePlacements.Checked)
                scnr.Scenery.AddRange(Scenery.Read(map, scnrOffset, sbspChunkIndex));

            //Find Flag Base
            int flagBaseIndex = Array.FindIndex(sceneryPalette, (Palette p) => { return p.Item.Path == "objects\\multi\\flag_base\\flag_base"; });

            //Generate Netgame Flags
            int baseCount = NetgameFlag.Generate(scnr, aiSquadPlacementSelector.AISquads, cbAddFlagBasePlacements.Checked, flagBaseIndex);

            //Create Item Collections
            if (cbUseEntity.Checked)
            {
                Directory.CreateDirectory(TagOutputDir);

                Palette.OutputItemCollections(outputFiles, weaponPlacements, weaponPalette, TagOutputDir, "multiplayer\\single_weapons\\");
                Palette.OutputItemCollections(outputFiles, vehiclePlacements, vehiclePalette, TagOutputDir, "multiplayer\\single_vehicles\\");
                Palette.OutputItemCollections(outputFiles, equipmentPlacements, equipmentPalette, TagOutputDir, "multiplayer\\powerups\\");
                Palette.OutputItemCollections(outputFiles, aiSquadPlacementSelector.AISquads, vehiclePalette, TagOutputDir, "multiplayer\\single_vehicles\\");

                //Create Netgame Equipment From Placements In Faux SCNR
                StartingEquipment.Generate(scnr, weaponPalette);

                NetgameEquipment.Generate(scnr, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex, !cbRemoveScripts.Checked, weaponPlacements, weaponPalette, "multiplayer\\single_weapons\\", new MapStream.StatusChangeHandler(StatusChanged));
                if(cbConvertAIVehicleSquads.Checked)
                    NetgameEquipment.Generate(scnr, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex, aiSquadPlacementSelector.AISquads, vehiclePalette, "multiplayer\\single_vehicles\\", new MapStream.StatusChangeHandler(StatusChanged));
                NetgameEquipment.Generate(scnr, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex, !cbRemoveScripts.Checked, vehiclePlacements, vehiclePalette, "multiplayer\\single_vehicles\\", new MapStream.StatusChangeHandler(StatusChanged));
                NetgameEquipment.Generate(scnr, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex, !cbRemoveScripts.Checked, equipmentPlacements, equipmentPalette, "multiplayer\\powerups\\", new MapStream.StatusChangeHandler(StatusChanged));

                PlayerStartingLocation.Generate(scnr, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex, bipedPlacements, new MapStream.StatusChangeHandler(StatusChanged));
                if (cbConvertAISquadsToPlayerSpawns.Checked)
                    PlayerStartingLocation.Generate(scnr, aiSquadPlacementSelector.AISquads, aiSquadPlacementSelector.AIZones, (cbUseAllSBSP.Checked) ? -1 : sbspChunkIndex, cbConvertAIVehicleSquads.Checked, new MapStream.StatusChangeHandler(StatusChanged));

                if (cbGenerateMultiplayerLevelData.Checked)
                {
                    LevelData levelData = LevelData.Read(map, scnrOffset);
                    if (levelData != null && levelData.CampaignLevel != null)
                    {
                        levelData.MultiplayerLevel = LevelData.MultiplayerLevelData.Generate(levelData.CampaignLevel, baseCount);
                        levelData.CampaignLevel = null;
                        scnr.LevelData = levelData;
                    }
                }

                if (cbAddRespawnZones.Checked)
                    SpawnData.Generate(scnr, aiSquadPlacementSelector.AISquads);

                scnr.Output(outputFiles, TagOutputDir);
            }
            else
            {
                //int itmcStartOffset = DataShifter.CreateMetaSpace(map, ITMCSize * totalCollectionCount);
                //map.Position = itmcStartOffset;
                //Palette.WriteItemCollections(map, weaponPalette);
                //Palette.WriteItemCollections(map, vehiclePalette);
                //Palette.WriteItemCollections(map, equipmentPalette);

                //Add Tags To Index
                //map.Position = DataShifter.CreateIndexSpace(map, totalCollectionCount);
                //for (int i = 0; i < totalCollectionCount; i++)
                //    DataShifter.WriteTagInfoIndexItem(map, "itmc", itmcStartOffset + (i * ITMCSize), ITMCSize);

                ////Create itmc Tag Paths
                //string[] tagPaths = new string[totalCollectionCount];
                //CreateItmcTagPaths(tagPaths, 0, "multiplayer\\single_weapons\\", weaponPalette);
                //CreateItmcTagPaths(tagPaths, weaponPalette.Length, "multiplayer\\single_vehicles\\", vehiclePalette);

                ////Add Tag Paths To File Table
                //map.Position = DataShifter.CreateFileTableSpace(map, CalculateTagPathInsertSize(tagPaths));
                //DataShifter.WriteStrings(map, tagPaths);
                ////TODO: Add Path Lengths To File Table Index

                //map.Index.TagCount += totalCollectionCount;

                //And Create Netgame Equipment From Placements In SCNR
            }
            

            //Update Scripts
            foreach (ScriptingSyntaxDatum datum in Script.Current.SyntaxDatums)
            {
                if (datum.Keep == false)
                {
                    datum.Previous.SetSibling(Script.Current.SyntaxDatums, datum.SiblingIndex, datum.SiblingIndexSalt);
                }
            }
            ScriptingSyntaxDatum.Write(map, scnrOffset, Script.Current.SyntaxDatums);

            //Update Placements
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.BipedsOffset, Placement.BipedsPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.EquipmentOffset, Placement.EquipmentPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.VehiclesOffset, Placement.VehiclesPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.WeaponsOffset, Placement.WeaponsPlacementChunkSize);

            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.SceneryOffset, Placement.SceneryPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.MachinesOffset, Placement.MachinesPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.ControlsOffset, Placement.ControlsPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.LightFixturesOffset, Placement.LightFixturesPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.SoundSceneryOffset, Placement.SoundSceneryPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.LightVolumesOffset, Placement.LightVolumesPlacementChunkSize);
            Placement.Update(map, scnrOffset, sbspChunkIndex, cboAIDifficulty.SelectedIndex, Placement.CratesOffset, Placement.CratesPlacementChunkSize);

            if (cbRemoveAI.Checked == false)
            {
                Flock.UpdateOriginBSP(map, scnrOffset, sbspChunkIndex);

                AIZone.Update(aiSquadPlacementSelector.AIZones, sbspChunkIndex);
                AIZone.Write(map, scnrOffset, aiSquadPlacementSelector.AIZones);
            }

            if (cbAddPlayerSpawns.Checked)
            {
                //If Selected By User, Add Player Spawns
            }

            if (cbReplaceMenu.Checked)
            {
                //If Selected By User, Replace Menu Meta and Raw
            }

            if (cbAddTags.Checked)
            {
                //If Selected By User, Add Multiplayer Tags
                if (cbUseEntity.Checked)
                    outputFiles.Add(Extracts.MultiplayerTags);
            }

            if (cbRemoveBipeds.Checked)
            {
                //If Selected By User, Null Bipeds
                Placement.NullPlacements(map, scnrOffset, Placement.BipedsOffset, Placement.BipedsPlacementChunkSize, !cbRemoveScripts.Checked);
            }

            if (cbRemoveWeapons.Checked)
            {
                //If Selected By User, Null Weapons
                Placement.NullPlacements(map, scnrOffset, Placement.WeaponsOffset, Placement.WeaponsPlacementChunkSize, !cbRemoveScripts.Checked);
            }

            if (cbRemoveVehicles.Checked)
            {
                //If Selected By User, Null Vehicles
                Placement.NullPlacements(map, scnrOffset, Placement.VehiclesOffset, Placement.VehiclesPlacementChunkSize, !cbRemoveScripts.Checked);
            }

            if (cbRemoveEquipment.Checked)
            {
                //If Selected By User, Null Equipment
                Placement.NullPlacements(map, scnrOffset, Placement.EquipmentOffset, Placement.EquipmentPlacementChunkSize, !cbRemoveScripts.Checked);
            }

            const long Empty = 0;
            if (cbRemoveBSPSwitchVolumes.Checked)
            {
                //If Selected By User, Null BSP Switch Volumes
                map.Position = scnrOffset + 304;
                map.Write(Empty);
            }

            if (cbRemoveAI.Checked)
            {
                //If Selected By User, Null AI
                map.Position = scnrOffset + 336;
                map.Write(Empty);
                map.Position = scnrOffset + 344;
                map.Write(Empty);
                map.Position = scnrOffset + 352;
                map.Write(Empty);
                map.Position = scnrOffset + 360;
                map.Write(Empty);
                map.Position = scnrOffset + 376;
                map.Write(Empty);
                map.Position = scnrOffset + 576;
                map.Write(Empty);
                map.Position = scnrOffset + 584;
                map.Write(Empty);
            }
            else
            {
                //Write Gravemind Settings
                int matgOffset = map.Tags[map.Index.GlobalsID, Tags.SearchType.ID].Offset;
                map.ReadReflexiveAt(matgOffset + 248);
                long aiSettingsOffset = map.Position;

                AIDifficulty aiSettings = new AIDifficulty();
                aiSettings.ReadFrom(map);
                aiSettings.SetAll(cboAIDifficulty.SelectedIndex);
                map.Position = aiSettingsOffset;
                aiSettings.WriteTo(map);

                //Set Difficulty and Write AI Squads
                AISquad.SetDifficulty(aiSquadPlacementSelector.AISquads, cboAIDifficulty.SelectedIndex, cbEnableAllAISpawns.Checked, cbEnableAIRespawns.Checked, cbMajorAIUpgrade.Checked);
                AISquad.Write(map, scnrOffset, aiSquadPlacementSelector.AISquads);
            }

            if (cbRemoveCinematics.Checked)
            {
                //If Selected By User, Null Cinematics
                map.Position = scnrOffset + 480;
                map.Write(Empty);
                map.Position = scnrOffset + 488;
                map.Write(Empty);
                map.Position = scnrOffset + 496;
                map.Write(Empty);
            }

            if (cbRemoveScripts.Checked)
            {
                //If Selected By User, Null Scripts
                map.Position = scnrOffset + 432;
                map.Write(Empty);
                map.Position = scnrOffset + 440;
                map.Write(Empty);
                map.Position = scnrOffset + 472;
                map.Write(Empty);
            }

            //Set Map Type to Multiplayer
            map.WriteAt(scnrOffset + 16, (short)1, false);

            if (cbSetLocationNames.Checked)
            {
                //If Selected By User, Set Territory Names To SP Location Names
                byte[] data = map.ReadBytesAt(scnrOffset + 952, 8, false);
                map.WriteAt(scnrOffset + 928, data, false);
            }

            if (cbUseEntity.Checked)
            {
                //Ouput Entity Info File
                FileStream fs = new FileStream(Path.Combine(Path.GetDirectoryName(map.FilePath), "Tags.info"), FileMode.Create);
                StreamWriter sw = new StreamWriter(fs);
                foreach (string s in outputFiles)
                    sw.WriteLine(s);
                //for (int i = 0; i < outputFiles.Count - 1; i++)
                //    sw.WriteLine(outputFiles[i]);
                //sw.Write(outputFiles[outputFiles.Count - 1]);
                sw.Flush();
                sw.Close();
            }

            if (cbResign.Checked) map.Resign();

            map.Flush();
            map.Close();

            gpOptions.Enabled = true;
            cmdStart.Enabled = true;
        }

        private void WritePlayerRepresentations(string bipd1, string hands1, string body1, string bipd2, string hands2, string body2)
        {
            int reflexiveStartOffset = (int)map.Position;
            Dependancy dependacy = new Dependancy();
            for (int i = 0; i < 2; i++)
            {
                int chunkStartOffset = reflexiveStartOffset + (188 * 2 * i);

                //Write Player 1
                map.Position = chunkStartOffset;

                dependacy.ClassString = "mode";
                dependacy.Path = hands1;
                map.Write(dependacy);

                dependacy.Path = body1;
                map.Write(dependacy);

                map.Position = chunkStartOffset + 176;
                dependacy.ClassString = "bipd";
                dependacy.Path = bipd1;
                map.Write(dependacy);

                //Write Player 2
                map.Position = chunkStartOffset + 188;

                dependacy.ClassString = "mode";
                dependacy.Path = hands2;
                map.Write(dependacy);

                dependacy.Path = body2;
                map.Write(dependacy);

                map.Position = chunkStartOffset + 188 + 176;
                dependacy.ClassString = "bipd";
                dependacy.Path = bipd2;
                map.Write(dependacy);
            }
        }

        private void CreateItmcTagPaths(string[] tagPaths, int startIndex, string prefix, Palette[] palette)
        {
            for (int i = 0; i < palette.Length; i++)
                tagPaths[i + startIndex] = prefix + Path.GetFileName(palette[i].Item.Path) + "\0";
        }

        private int CalculateTagPathInsertSize(string[] tagPaths)
        { 
            int size = 0;
            for (int i = 0; i < tagPaths.Length; i++)
                size += tagPaths[i].Length;
            return size;
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void openSinglePlayerMapToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (OFD.ShowDialog() == DialogResult.OK)
            {
                this.Text = "Mystery v7 - " + new FileInfo(OFD.FileName).Name;
                map = new MapStream(OFD.FileName, new MapStream.StatusChangeHandler(StatusChanged));
                bipdTags = map.Tags.GetStringArrayOfClass("bipd");
                modeTags = map.Tags.GetStringArrayOfClass("mode");
                sbspTags = map.Tags.GetStringArrayOfClass("sbsp");
                //H2.TagStructures.scnr scnr = (H2.TagStructures.scnr)map.Tags.LoadTag(map.Index.ScnrID, Tags.SearchType.ID);
                //H2.TagStructures.matg matg = (H2.TagStructures.matg)map.Tags.LoadTag(map.Index.GlobalsID, Tags.SearchType.ID);
                //H2.TagStructures.sncl sncl = (H2.TagStructures.sncl)map.Tags.LoadTag("sncl", "sound\\sound_classes");
                //H2.TagStructures.spk_ spk_ = (H2.TagStructures.spk_)map.Tags.LoadTag("spk!", "sound\\combat_dialogue_constants");


                //Get Scnr Offset
                int scnrOffset = map.Tags[map.Index.ScnrID, Tags.SearchType.ID].Offset;
                scriptViewer.ScnrOffset = scnrOffset;
                scriptViewer.Map = map;

                Script script = Script.Read(map, scnrOffset);

                //Read AI
                aiSquadPlacementSelector.SetInfo(AISquad.Read(map, scnrOffset), AIZone.Read(map, scnrOffset));

                map.Close();

                Array.Sort<string>(bipdTags);
                Array.Sort<string>(modeTags);

                linkMultiplayerScenarioToolStripMenuItem.Enabled = true;
                transferStringsToolStripMenuItem.Enabled = true;
                cmdScripts.Enabled = true;
                cmdSelectAISquads.Enabled = true;
                gpOptions.Enabled = true;
                cmdStart.Enabled = true;

                cboSBSP.Items.Clear();
                cboSBSP.Items.AddRange(sbspTags);
                cboSBSP.SelectedIndex = 0;

                cboPlayerUnit1.Items.Clear();
                cboPlayerUnit1.Items.AddRange(bipdTags);
                cboPlayerUnit1.SelectedIndex = cboPlayerUnit1.Items.IndexOf("objects\\characters\\masterchief\\masterchief_mp");
                if (cboPlayerUnit1.SelectedIndex == -1) cboPlayerUnit1.SelectedIndex = cboPlayerUnit1.Items.IndexOf("objects\\characters\\masterchief\\masterchief");
                if (cboPlayerUnit1.SelectedIndex == -1) cboPlayerUnit1.SelectedIndex = cboPlayerUnit1.Items.IndexOf("objects\\characters\\dervish\\dervish");

                cboPlayerBody1.Items.Clear();
                cboPlayerBody1.Items.AddRange(modeTags);
                cboPlayerBody1.SelectedIndex = cboPlayerBody1.Items.IndexOf("objects\\characters\\masterchief\\fp_body\\fp_body");
                if (cboPlayerBody1.SelectedIndex == -1) cboPlayerBody1.SelectedIndex = cboPlayerBody1.Items.IndexOf("objects\\characters\\dervish\\fp_body\\fp_body");
                if (cboPlayerBody1.SelectedIndex == -1) cboPlayerBody1.SelectedIndex = cboPlayerBody1.Items.IndexOf("objects\\characters\\masterchief\\masterchief");

                cboPlayerHands1.Items.Clear();
                cboPlayerHands1.Items.AddRange(modeTags);
                cboPlayerHands1.SelectedIndex = cboPlayerHands1.Items.IndexOf("objects\\characters\\masterchief\\fp\\fp");
                if (cboPlayerHands1.SelectedIndex == -1) cboPlayerHands1.SelectedIndex = cboPlayerHands1.Items.IndexOf("objects\\characters\\dervish\\fp\\fp");
                if (cboPlayerHands1.SelectedIndex == -1) cboPlayerHands1.SelectedIndex = cboPlayerHands1.Items.IndexOf("objects\\characters\\masterchief\\masterchief");

                cboPlayerUnit2.Items.Clear();
                cboPlayerUnit2.Items.AddRange(bipdTags);
                cboPlayerUnit2.SelectedIndex = cboPlayerUnit2.Items.IndexOf("objects\\characters\\elite\\elite_mp");
                if (cboPlayerUnit2.SelectedIndex == -1) cboPlayerUnit2.SelectedIndex = cboPlayerUnit2.Items.IndexOf("objects\\characters\\dervish\\dervish");
                if (cboPlayerUnit2.SelectedIndex == -1) cboPlayerUnit2.SelectedIndex = cboPlayerUnit2.Items.IndexOf("objects\\characters\\masterchief\\masterchief");

                cboPlayerBody2.Items.Clear();
                cboPlayerBody2.Items.AddRange(modeTags);
                cboPlayerBody2.SelectedIndex = cboPlayerBody2.Items.IndexOf("objects\\characters\\elite\\fp_body\\fp_body");
                if (cboPlayerBody2.SelectedIndex == -1) cboPlayerBody2.SelectedIndex = cboPlayerBody2.Items.IndexOf("objects\\characters\\dervish\\fp_body\\fp_body");
                if (cboPlayerBody2.SelectedIndex == -1) cboPlayerBody2.SelectedIndex = cboPlayerBody2.Items.IndexOf("objects\\characters\\masterchief\\fp_body\\fp_body");

                cboPlayerHands2.Items.Clear();
                cboPlayerHands2.Items.AddRange(modeTags);
                cboPlayerHands2.SelectedIndex = cboPlayerHands2.Items.IndexOf("objects\\characters\\elite\\fp_arms\\fp_arms");
                if (cboPlayerHands2.SelectedIndex == -1) cboPlayerHands2.SelectedIndex = cboPlayerHands2.Items.IndexOf("objects\\characters\\dervish\\fp\\fp");
                if (cboPlayerHands2.SelectedIndex == -1) cboPlayerHands2.SelectedIndex = cboPlayerHands2.Items.IndexOf("objects\\characters\\masterchief\\fp\\fp");

                scriptViewer.LoadScripts();
            }
        }

        private void linkMultiplayerScenarioToolStripMenuItem_Click(object sender, EventArgs e)
        {
            map = new MapStream(map.FilePath, new MapStream.StatusChangeHandler(StatusChanged));

            TransferStringsDialog LMD = new TransferStringsDialog();
            LMD.SetMulgTags(map.Tags.GetStringArrayOfClass("mulg"));
            LMD.SetScnrTags(map.Tags.GetStringArrayOfClass("scnr"));
            if (LMD.ShowDialog() == DialogResult.Cancel)
            {
                map.Close();
                return;
            }

            gpOptions.Enabled = false;
            cmdStart.Enabled = false;

            //Get MP Scnr Offset
            TagInfo ti = map.Tags["scnr", LMD.MpScnrPath];

            int mpScnrOffset = ti.Offset;

            //Get SP Scnr Offset
            int spScnrOffset = map.Tags[map.Index.ScnrID, Tags.SearchType.ID].Offset;

            //Link Reflexives
            int chunkCount = map.ReadInt32At(mpScnrOffset + PlayerStartingLocation.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + PlayerStartingLocation.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + PlayerStartingLocation.Offset + 4, map.ReadInt32At(mpScnrOffset + PlayerStartingLocation.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + Scenery.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + Scenery.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + Scenery.Offset + 4, map.ReadInt32At(mpScnrOffset + Scenery.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + TriggerVolume.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + TriggerVolume.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + TriggerVolume.Offset + 4, map.ReadInt32At(mpScnrOffset + TriggerVolume.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + NetgameFlag.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + NetgameFlag.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + NetgameFlag.Offset + 4, map.ReadInt32At(mpScnrOffset + NetgameFlag.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + NetgameEquipment.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + NetgameEquipment.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + NetgameEquipment.Offset + 4, map.ReadInt32At(mpScnrOffset + NetgameEquipment.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + StartingEquipment.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + StartingEquipment.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + StartingEquipment.Offset + 4, map.ReadInt32At(mpScnrOffset + StartingEquipment.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + KillTriggers.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + KillTriggers.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + KillTriggers.Offset + 4, map.ReadInt32At(mpScnrOffset + KillTriggers.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + SpawnData.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + SpawnData.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + SpawnData.Offset + 4, map.ReadInt32At(mpScnrOffset + SpawnData.Offset + 4, false), false);
            }

            chunkCount = map.ReadInt32At(mpScnrOffset + LevelData.Offset, false);
            if (chunkCount > 0)
            {
                map.WriteAt(spScnrOffset + LevelData.Offset, chunkCount, false);
                map.WriteAt(spScnrOffset + LevelData.Offset + 4, map.ReadInt32At(mpScnrOffset + LevelData.Offset + 4, false), false);
            }

            //Get UI Globals
            int uiGlobalsID = -1;
            if (map.Tags.Contains("wgtz", @"ui\multiplayer")) uiGlobalsID = map.Tags["wgtz", @"ui\multiplayer"].ID;
            else uiGlobalsID = map.Tags["wgtz", @"ui\single_player"].ID;

            //Get Globals Offset
            int globalsOffset = map.Tags[map.Index.GlobalsID, Tags.SearchType.ID].Offset;
            
            //Set mulg
            H2.DataTypes.Dependancy mp_mulg = new Dependancy();
            mp_mulg.ClassString = "mulg";
            mp_mulg.Path = LMD.MulgPath;
            map.WriteAt(globalsOffset + 360, mp_mulg, false);

            //Set UI Globals
            map.ReadReflexiveAt(globalsOffset + 272);
            map.Position += 140;
            map.Write(-1);
            map.Position += 4;
            map.Write(uiGlobalsID);

            //Optionally Link MP Bipds
            int mcMpIndex = Array.IndexOf<string>(bipdTags, "objects\\characters\\masterchief\\masterchief_mp");
            int mcBodyIndex = Array.IndexOf<string>(modeTags, "objects\\characters\\masterchief\\fp_body\\fp_body");
            int mcArmsIndex = Array.IndexOf<string>(modeTags, "objects\\characters\\masterchief\\fp\\fp");

            int eliteMpIndex = Array.IndexOf<string>(bipdTags, "objects\\characters\\elite\\elite_mp");
            int eliteBodyIndex = Array.IndexOf<string>(modeTags, "objects\\characters\\elite\\fp_body\\fp_body");
            int eliteArmsIndex = Array.IndexOf<string>(modeTags, "objects\\characters\\elite\\fp_arms\\fp_arms");

            if (mcMpIndex != -1 && mcBodyIndex != -1 && mcArmsIndex != -1 && eliteMpIndex != -1 && eliteBodyIndex != -1 && eliteArmsIndex != -1)
            {
                //Go to Player Representations Reflexive
                map.Position = map.Tags[map.Index.GlobalsID, Tags.SearchType.ID].Offset + 0x138;
                chunkCount = map.ReadReflexive();
                if (chunkCount != 4) throw new Exception("Incorrect Player Representaion Chunk Count! Map Might Be Corrupt!");
                WritePlayerRepresentations(
                    "objects\\characters\\masterchief\\masterchief_mp", 
                    "objects\\characters\\masterchief\\fp\\fp", 
                    "objects\\characters\\masterchief\\fp_body\\fp_body",
                    "objects\\characters\\elite\\elite_mp",
                    "objects\\characters\\elite\\fp_arms\\fp_arms",
                    "objects\\characters\\elite\\fp_body\\fp_body"
                    );
            }

            //Fix Multiplayer Weapons
            InitPistolString();
            FixMPWeapon(map.Tags["weap", @"objects\weapons\multiplayer\assault_bomb\assault_bomb"]);
            FixMPWeapon(map.Tags["weap", @"objects\weapons\multiplayer\ball\ball"]);
            FixMPWeapon(map.Tags["weap", @"objects\weapons\multiplayer\flag\flag"]);
            
            if (cbResign.Checked) map.Resign();

            map.Flush();
            map.Close();

            gpOptions.Enabled = true;
            cmdStart.Enabled = true;
        }

        char[] PistolString;

        private void InitPistolString()
        {
            PistolString = new char[128];
            PistolString[0] = 'p';
            PistolString[1] = 'i';
            PistolString[2] = 's';
            PistolString[3] = 't';
            PistolString[4] = 'o';
            PistolString[5] = 'l';
        }

        private void FixMPWeapon(TagInfo tagInfo)
        {
            if (tagInfo == null) return;

            map.WriteAt(tagInfo.Offset + 648, new StringID(new string(PistolString)), false);
        }

        private void StatusChanged(string Status)
        {
            lstOutput.Items.Add(Status.Replace("Map Closed", "Waiting For User..."));
            lstOutput.SelectedIndex = lstOutput.Items.Count - 1;
        }

        private void cmdUncheckAll_Click(object sender, EventArgs e)
        {
            foreach (CheckBox cb in gpOptions.Controls.OfType<CheckBox>())
                if (cb.Enabled && cb != cbUseAllSBSP) cb.Checked = false;
        }

        private void cmdCheckAll_Click(object sender, EventArgs e)
        {
            foreach (CheckBox cb in gpOptions.Controls.OfType<CheckBox>())
                if (cb.Enabled && cb != cbUseAllSBSP) cb.Checked = true;
        }

        private void cb_CheckedChanged(object sender, EventArgs e)
        { 
            cboSBSP.Enabled = !cbUseAllSBSP.Checked;
            cboAIDifficulty.Enabled = !cbRemoveAI.Checked;
        }

        private void cmdSelectAISquads_Click(object sender, EventArgs e)
        {
            aiSquadPlacementSelector.Show();
        }

        private void cmdScripts_Click(object sender, EventArgs e)
        {
            scriptViewer.Show();
        }

        private void transferStringsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (OFD.ShowDialog() != DialogResult.OK) return;

            gpOptions.Enabled = false;
            cmdStart.Enabled = false;

            MapStream otherMap = new MapStream(OFD.FileName, null);

            //Read Strings To Transfer
            int otherStringCount = otherMap.Unicode.English.StringCount;
            const int indexEntrySize = 8;

            int otherStringsIndexOffset = otherMap.Unicode.English.StringIndexOffset;

            int otherStringsOffset = otherMap.Unicode.English.StringTableOffset;
            int otherStringsSize = otherMap.Unicode.English.StringTableSize;

            UnicodeIndex[] otherIndices = UnicodeIndex.Read(otherMap, otherMap.Unicode.English);
            //string[] otherUnicodeStringData = otherMap.Unicode.English.ReadTable();
            byte[] otherUnicodeStringData = otherMap.ReadBytesAt(otherStringsOffset, otherStringsSize, false);

            TagInfo[] otherUnicTagInfos = otherMap.Tags.GetTagInfoArrayOfClass("unic");
            string[] otherUnicTagName = otherMap.Tags.GetStringArrayOfClass("unic");
            UnicodeTag[] otherUnicTags = new UnicodeTag[otherUnicTagInfos.Length];
            
            //List<UnicodeReference> otherReferences = UnicodeReference.Create(otherUnicodeStringData, otherIndices, otherUnicTags);

            for (int i = 0; i < otherUnicTags.Length; i++)
            {
                otherMap.Position = otherUnicTagInfos[i].Offset;
                otherUnicTags[i] = UnicodeTag.Read(otherMap);
            }

            otherMap.Close();

            map.ReOpen();

            //Read Existing Strings
            int stringCount = map.Unicode.English.StringCount;
            int stringsTableOffset = map.Unicode.English.StringTableOffset;
            int stringsIndexOffset = map.Unicode.English.StringIndexOffset;
            int stringsSize = map.Unicode.English.StringTableSize;

            UnicodeIndex[] indices = UnicodeIndex.Read(map, map.Unicode.English);
            //string[] unicodeStringData = map.Unicode.English.ReadTable();
            byte[] unicodeStringData = map.ReadBytesAt(stringsTableOffset, stringsSize, false);

            TagInfo[] unicTagInfos = map.Tags.GetTagInfoArrayOfClass("unic");
            UnicodeTag[] unicTags = new UnicodeTag[unicTagInfos.Length];
            for (int i = 0; i < unicTagInfos.Length; i++)
            {
                map.Position = unicTagInfos[i].Offset;
                unicTags[i] = UnicodeTag.Read(map);
            }

            #region Transfer Some
            //List<UnicodeReference> references = UnicodeReference.Create(unicodeStringData, indices, unicTags);

            //references = UnicodeReference.Combine(references, otherReferences);

            //map.Position = stringsIndexOffset;
            //stringCount = UnicodeReference.WriteIndexTable(map, references, 0);
            //map.Write(new byte[512L - (map.Position % 512L)]); //Pad
            //stringsTableOffset = (int)map.Position;
            //stringsSize = UnicodeReference.WriteStringTable(map, references);
            //map.Write(new byte[512L - (map.Position % 512L)]); //Pad
            
            //UnicodeReference.WriteTagData(map, references);
            #endregion

            #region Transfer All

            //Update Transfered Strings
            for (int i = 0; i < otherIndices.Length; i++)
                otherIndices[i].StringOffset += stringsSize;

            for (int i = 0; i < otherUnicTags.Length; i++)
                otherUnicTags[i].EnglishStringIndex += (ushort)stringCount;

            //Write Index
            map.Position = stringsIndexOffset;
            map.Position += stringCount * indexEntrySize;//UnicodeIndex.Write(map, indices);
            UnicodeIndex.Write(map, otherIndices);
            map.Write(new byte[512L - (map.Position % 512L)]); //Pad

            //Write Strings
            stringsTableOffset = (int)map.Position;
            map.Write(unicodeStringData);
            map.Write(otherUnicodeStringData);
            map.Write(new byte[512L - (map.Position % 512L)]); //Pad

            //Write Tags
            for (int i = 0; i < otherUnicTags.Length; i++)
            {
                if (map.Tags.Contains("unic", otherUnicTagName[i]))
                {
                    map.MoveToTag("unic", otherUnicTagName[i]);
                    UnicodeTag.Write(map, otherUnicTags[i]);
                }
            }

            //Update Unicode Info
            stringCount += otherStringCount;
            stringsSize += otherStringsSize;

            #endregion

            //Write Unicode Info
            map.Unicode.English.StringCount = stringCount;
            map.Unicode.English.StringTableSize = stringsSize;
            map.Unicode.English.StringTableOffset = stringsTableOffset;
            map.Unicode.Japanese.StringCount = 0;
            map.Unicode.Japanese.StringTableSize = 0;

            if (cbResign.Checked) map.Resign();

            map.Flush();
            map.Close();

            gpOptions.Enabled = true;
            cmdStart.Enabled = true;
        }
    }
}
